<template>
  <div class="flex-grow d-flex-col">
    <el-table
      :data="tableData"
      height="auto"
      style="width: 100%"
      ref="table"
      v-bind="tableProp"
      @cell-dblclick="cellClick"
      :key="keys"
    >
      <template slot="empty">
        <slot name="empty">
          <el-empty style="margin-bottom: 50px"></el-empty>
        </slot>
      </template>
      <el-table-column
        v-for="(item, index) in columns.filter(column => !column.hidden)"
        v-bind="item"
        :key="item.prop"
      >
        <template v-slot:header>
          <slot :name="`${item.prop}-header`" :label="item.label">{{
            item.label
          }}</slot>
        </template>
        <template slot-scope="scope">
          <div
            @click.stop="() => false"
            v-if="isEditShow(scope.row, item.prop)"
          >
            <el-input
              v-if="formProps.props[item.prop].type === 'text'"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="methods[item.prop]"
            >
            </el-input>
            <el-input
              v-model="scope.row[item.prop]"
              v-if="formProps.props[item.prop].type === 'number'"
              v-bind="getFormItemProp(item.prop)"
              v-on="methods[item.prop]"
              @input="
                value =>
                  handleNumInput(
                    scope.$index,
                    item.prop,
                    value,
                    formProps.props[item.prop].precision
                  )
              "
              @change="
                value =>
                  handleNumChange(scope.$index, item.prop, value, scope.row)
              "
            >
            </el-input>
            <el-date-picker
              v-if="isDateType(formProps.props[item.prop].type)"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="methods[item.prop]"
              class="w-100"
            >
            </el-date-picker>
            <el-time-picker
              v-if="formProps.props[item.prop].type === 'time'"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="methods[item.prop]"
              class="w-100"
            >
            </el-time-picker>
            <el-select
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="methods[item.prop]"
              v-if="formProps.props[item.prop].type === 'select'"
              class="w-100"
            >
              <el-option
                v-for="item in formProps.props[item.prop].options"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              >
              </el-option>
            </el-select>
            <el-switch
              v-if="formProps.props[item.prop].type === 'switch'"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="methods[item.prop]"
            >
            </el-switch>
            <el-radio-group
              v-if="formProps.props[item.prop].type === 'radio'"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="methods[item.prop]"
            >
              <el-radio
                style="padding:5px 0"
                v-for="option in formProps.props[item.prop].options"
                :key="option.value"
                :label="option.value"
                >{{ option.label }}</el-radio
              >
            </el-radio-group>
            <el-cascader
              v-if="formProps.props[item.prop].type === 'cascader'"
              v-model="scope.row[item.prop]"
              v-bind="getFormItemProp(item.prop)"
              v-on="methods[item.prop]"
              class="w-100"
            ></el-cascader>
            <slot
              v-if="formProps.props[item.prop].type === 'custom'"
              :name="item.prop + '-edit'"
              :row="scope.row"
              :value="scope.row[item.prop]"
              :$index="scope.$index"
            ></slot>
          </div>

          <template v-else>
            <slot
              :name="item.prop"
              :row="scope.row"
              :value="scope.row[item.prop]"
              :$index="scope.$index"
            >
              <template v-if="item.formatter">
                {{
                  item.formatter(
                    scope.row,
                    $refs.table.columns[index],
                    scope.row[item.prop],
                    index
                  )
                }}
              </template>
              <template v-else>
                <span
                  v-if="
                    ['select', 'radio'].includes(
                      formProps.props[item.prop].type
                    )
                  "
                >
                  {{
                    getLabelByValue(
                      scope.row[item.prop],
                      formProps.props[item.prop].options
                    )
                  }}</span
                >
                <span v-if="formProps.props[item.prop].type === 'cascader'">
                  {{
                    getLabelByValue(
                      scope.row[item.prop],
                      formProps.props[item.prop].options
                    )
                  }}</span
                >
                <el-switch
                  v-if="formProps.props[item.prop].type === 'switch'"
                  v-model="scope.row[item.prop]"
                  disabled
                  v-bind="getFormItemProp(item.prop)"
                  v-on="methods[item.prop]"
                ></el-switch>
                <template v-if="formProps.props[item.prop].type === 'checkbox'">
                  <template v-if="scope.row[item.prop].length">
                    {{
                      getLabelByValue(
                        scope.row[item.prop],
                        formProps.props[item.prop].options
                      )
                    }}
                  </template>
                  <template v-else>
                    <span>{{ "" }}</span>
                  </template>
                </template>
                <slot
                  v-if="
                    ![
                      'select',
                      'radio',
                      'cascader',
                      'switch',
                      'checkbox'
                    ].includes(formProps.props[item.prop].type)
                  "
                >
                  <span>{{ scope.row[item.prop] }} </span>
                </slot>
              </template>
            </slot>
          </template>
        </template>
      </el-table-column>

      <el-table-column
        align="center"
        label="操作"
        :fixed="isActionFixed ? 'right' : false"
        :width="isActionFixed ? 125 : ''"
      >
        <template slot-scope="scope">
          <el-link
            v-if="scope.row[rowKey] == activeRow.id && activeRow.isEditing"
            type="primary"
            :disabled="saveDisiabled"
            @click="saveEdit(scope.row, scope.$index)"
            >确定</el-link
          >
          <el-link
            v-if="scope.row[rowKey] == activeRow.id && activeRow.isEditing"
            type="info"
            @click="cancelEdit(scope.$index)"
            >取消</el-link
          >
          <el-link
            v-if="
              editBtnVisible &&
                mode === 'rowEdit' &&
                scope.row[rowKey] !== activeRow.id
            "
            type="primary"
            @click="editRow(scope.row, scope.$index)"
            >编辑</el-link
          >

          <el-link
            v-if="
              deleteBtnVisible &&
                (scope.row[rowKey] !== activeRow.id || !activeRow.isEditing)
            "
            type="danger"
            @click="removeRow(scope.row, scope.$index)"
            >删除</el-link
          >
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
export default {
  name: "editable-grid",
  props: {
    columns: {
      type: Array,
      required: true
    },
    formProps: {
      type: Object,
      required: true
    },
    tableProps: {
      type: Object,
      default: () => ({})
    },
    rowKey: {
      type: String,
      default: "id"
    },
    mode: {
      type: String,
      required: true,
      default: "rowEdit" // 模式：默认rowEdit-行编辑，cellEdit-单元格编辑
    },
    datas: {
      type: Array,
      default: () => []
    },
    isActionFixed: {
      type: Boolean,
      default: true
    },
    editBtnVisible: {
      type: Boolean,
      default: true
    },
    deleteBtnVisible: {
      type: Boolean,
      default: true
    },
    change: {
      type: Function
    },
    extraBlur: {
      type: [Function, undefined]
    },
    extraChange: {
      type: [Function, undefined]
    }
  },
  data() {
    return {
      tableData: [],
      tableProp: {},
      templeteData: {},
      activeRow: {
        id: "",
        name: "",
        isEditing: false,
        index: ""
      },
      copeRow: null,
      newRowIndex: "",
      keys: true,
      keyForMode: "",
      saveDisiabled: false,
      methods: {}
    };
  },
  watch: {
    datas: {
      handler(val) {
        this.tableData = JSON.parse(JSON.stringify(val));
        this.saveDisiabled = false;
      },
      deep: true,
      immediate: true
    }
  },
  mounted() {
    // 初始合并table属性
    this.tableProp = Object.assign(
      {
        height: "auto",
        border: true,
        size: "small"
      },
      this.tableProps
    );
    // 添加公共change、blur事件
    this.methods = this.formProps.methods;
    this.addExtraChange().then(() => {
      this.addExtraBlur();
    });
  },
  methods: {
    addExtraChange() {
      return new Promise((resolve, reject) => {
        for (const key in this.formProps.props) {
          // 初始化template row
          if (
            ["checkbox", "cascader"].includes(this.formProps.props[key].type)
          ) {
            this.templeteData[key] = [];
          } else {
            this.templeteData[key] = "";
          }
          // cellEdit模式时，添加extraChange
          if (this.mode === "cellEdit") {
            if (this.methods[key]) {
              const existChange = this.methods[key].change;
              if (existChange) {
                this.methods[key].change = async val => {
                  await existChange(val);
                  this.extraChange(val);
                };
              } else {
                this.methods[key].change = this.extraChange;
              }
            } else {
              this.methods[key] = { change: this.extraChange };
            }
          }
        }
        resolve();
      });
    },
    addExtraBlur() {
      // cellEdit模式时，添加extraBlur
      if (this.mode === "cellEdit") {
        for (const key in this.formProps.props) {
          [{ event: "blur", callback: "extraBlur" }].forEach(item => {
            this.changeBlur2Change(
              item.event,
              this[item.callback],
              key,
              this.methods,
              this.formProps.props[key]
            );
          });
        }
      }
    },
    changeBlur2Change(event, callback, key, methods, prop) {
      if (!callback) return;
      const will2Change = ["switch", "radio"].includes(prop.type);
      const will2visibleChange = ["select", "cascader"].includes(prop.type);
      const eventName = will2Change
        ? "change"
        : will2visibleChange
        ? "visible-change"
        : event;
      if (methods[key]) {
        const existChange = methods[key][eventName];
        if (existChange) {
          // eslint-disable-next-line vue/no-mutating-props
          methods[key][eventName] = async value => {
            await existChange(value);
            callback({
              isSwitchOrRadio: will2Change,
              isSelectOrCascader: will2visibleChange,
              value
            });
          };
        } else {
          // eslint-disable-next-line vue/no-mutating-props
          methods[key][eventName] = async value => {
            callback({
              isSwitchOrRadio: will2Change,
              isSelectOrCascader: will2visibleChange,
              value
            });
          };
        }
      } else {
        // eslint-disable-next-line vue/no-mutating-props
        methods[key] = {
          [eventName]: async value => {
            callback({
              isSwitchOrRadio: will2Change,
              isSelectOrCascader: will2visibleChange,
              value
            });
          }
        };
      }
    },
    getFormItemProp(name) {
      const prop = this.formProps.props[name];
      if (prop.type === "number") {
        return Object.assign({}, prop, {
          type: "text",
          style: prop.style || { width: "100%" },
          size: this.tableProp.size
        });
      } else {
        return Object.assign({}, prop, {
          style: prop.style || { width: "100%" },
          size: this.tableProp.size
        });
      }
    },
    // 有input事件传入时，调用事件
    handleNumInput(index, name, value, precision) {
      const { methods } = this.formProps;
      this.tableData[index][name] = this.formatNumber(value, precision);
      const inputMethod = methods ? methods[name]?.input : undefined;
      typeof inputMethod === "function" && inputMethod(value);
    },
    // 有change事件传入时，调用事件
    handleNumChange(index, name, value, row) {
      const { methods } = this.formProps;
      this.tableData[index][name] = this.getNumberFromString(value);
      if (this.mode === "rowEdit") {
        const changeMethod = methods ? methods[name]?.change : undefined;
        typeof changeMethod === "function" && changeMethod(value, row);
      }
    },
    // 获取指定小数位数浮点数字符串
    formatNumber(value, precision = 0) {
      const isMinus = value.charAt(0) === "-";
      const numberValue = value
        .replace(/[^\d.]/g, "")
        .replace(new RegExp(`\\.{${precision},}`, "g"), ".")
        .replace(".", "$#$")
        .replace(/\./g, "")
        .replace("$#$", ".")
        .replace(new RegExp(`^(-)*(\\d+)\\.(\\d{${precision},}).*$`), "$1$2.$3")
        .replace(/^\./g, "");
      const minusMap = { true: "-", false: "" };
      const floatValue = parseFloat(minusMap[isMinus] + numberValue);
      if (isNaN(floatValue)) {
        return "";
      }
      const splitArray = numberValue.split(".");
      let strNumber = minusMap[isMinus] + numberValue;
      if (splitArray.length > 1) {
        const fixedStr = precision
          ? splitArray[1].substring(0, precision)
          : splitArray[1];
        if (splitArray.length > 2 || fixedStr) {
          strNumber = minusMap[isMinus] + splitArray[0] + "." + fixedStr;
        }
      } else {
        strNumber = minusMap[isMinus] + numberValue;
      }
      return strNumber;
    },
    // 获取浮点数
    getNumberFromString(value) {
      return isNaN(parseFloat(value)) ? 0 : parseFloat(value);
    },
    // 是否是elementUI el-date-picker组件types
    isDateType(type) {
      return (
        [
          "year",
          "month",
          "date",
          "dates",
          "week",
          "datetime",
          "datetimerange",
          "daterange",
          "monthrange"
        ].indexOf(type) >= 0
      );
    },
    // 根据value获取label
    getLabelByValue(ids, data) {
      const { getType, echoCascaderData } = this.$utils;
      if (getType(ids) === "Array") {
        const labels = ids.map(id => {
          return getType(id) === "Array"
            ? id.map(rid => echoCascaderData(rid, data)).join("/")
            : echoCascaderData(id, data);
        });
        return labels.join("，");
      } else {
        const label = this.$utils.echoCascaderData(ids, data);
        return label || "";
      }
    },
    //编辑框是否显示
    isEditShow(row, name) {
      const { rowKey, newRowIndex } = this;
      if (this.mode === "rowEdit") {
        return row[rowKey] === this.activeRow.id && this.activeRow.isEditing;
      }
      if (this.mode === "cellEdit") {
        if (typeof newRowIndex === "number") {
          return row[rowKey] === this.activeRow.id && this.activeRow.isEditing;
        } else {
          return row[rowKey] === this.keyForMode && name == this.activeRow.name;
        }
      }
      return false;
    },
    cancelEdit(index) {
      this.activeRow.isEditing = false;
      this.activeRow.id = "";
      this.tableData[index] = JSON.parse(JSON.stringify(this.copeRow));
      index === this.newRowIndex && this.tableData.splice(index, 1);
      this.newRowIndex = "";
    },
    saveEdit(row, index) {
      const { rowKey, change } = this;
      this.saveDisiabled = true;
      if (change) {
        // 新建时
        if (typeof this.newRowIndex === "number") {
          const newRow = JSON.parse(JSON.stringify(row));
          if (this.mode === "rowEdit") {
            newRow[rowKey] = ""; // 新建时 id（rowKey）得为空，
          }
          change({ row: newRow, index, type: "save" })
            .then(res => {
              if (res && typeof res === "object") {
                this.tableData[index] = JSON.parse(JSON.stringify(res));
              }
              if (res && typeof res === "string") {
                row[rowKey] = res;
              }
              console.log(1);
              this.activeRow.isEditing = false;
              this.activeRow.id = "";
              this.saveDisiabled = false;
              this.newRowIndex = "";
            })
            .catch(reject => {
              this.$message.error(reject);
              console.log(2);
              this.saveDisiabled = false;
              return;
            });
        } else {
          // 编辑时
          change({ row, index, type: "edit" })
            .then(res => {
              this.activeRow.isEditing = false;
              this.activeRow.id = "";
              this.saveDisiabled = false;
            })
            .catch(reject => {
              this.$message.error(reject);
              this.saveDisiabled = false;
              return;
            });
        }
      } else {
        this.activeRow.isEditing = false;
        this.activeRow.id = "";
        this.newRowIndex = "";
        this.saveDisiabled = false;
      }
    },
    editRow(row, index) {
      const { rowKey } = this;
      if (typeof this.newRowIndex === "number") {
        this.$message.error("请先保存新增行的数据");
        return;
      }
      this.activeRow.isEditing = true;
      if (this.activeRow.id && this.activeRow.id !== row[rowKey]) {
        this.tableData[this.activeRow.index] = JSON.parse(
          JSON.stringify(this.copeRow)
        );
      }
      this.copeRow = JSON.parse(JSON.stringify(this.tableData[index]));
      this.activeRow.index = index;
      this.activeRow.id = row[rowKey];
      this.newRowIndex = "";
    },
    removeRow(row, index) {
      const { rowKey, change } = this;
      this.$confirm("确定删除本行内容？", "提示", {
        type: "warning"
      })
        .then(() => {
          if (change) {
            change({ row, index, type: "delete" })
              .then(res => {
                this.$delete(this.tableData, index);
                const num = this.tableData.findIndex(
                  item => item[rowKey] == this.activeRow.id
                );
                if (num !== -1) {
                  this.activeRow.index = num;
                }
                this.$nextTick(() => {
                  this.keys = !this.keys;
                });
              })
              .catch(reject => {
                this.$message.error(reject);
              });
          } else {
            this.$delete(this.tableData, index);
            const num = this.tableData.findIndex(
              item => item[rowKey] == this.activeRow.id
            );
            if (num !== -1) {
              this.activeRow.index = num;
            }
            this.$nextTick(() => {
              this.keys = !this.keys;
            });
          }
        })
        .catch(() => {});
    },
    addRow(sort = "after") {
      const { rowKey } = this;
      if (this.activeRow.isEditing === true) {
        return this.$message.error("请先保存当前编辑行的数据");
      }
      const templete = Object.assign({}, this.templeteData, {
        [rowKey]: new Date().getTime() + ""
      });
      if (!this.tableData.length) {
        this.activeRow.id = templete[rowKey];
      }
      if (sort === "after") {
        this.tableData.push(JSON.parse(JSON.stringify(templete)));
      } else {
        this.tableData.unshift(JSON.parse(JSON.stringify(templete)));
      }
      this.newRowIndex = this.tableData.length - 1;
      this.copeRow = JSON.parse(JSON.stringify(templete));
      this.activeRow = {
        id: templete[rowKey],
        name: "",
        isEditing: true,
        index: this.tableData.length - 1
      };
      this.$nextTick(() => {
        this.keys = !this.keys;
      });
    },
    cellClick(row, column) {
      const { rowKey } = this;
      if (this.mode === "cellEdit") {
        this.$emit("cell-click", row);
      }
      this.keyForMode = row[rowKey];
      this.activeRow.name = column.property;
      this.$nextTick(() => {
        this.keys = !this.keys;
      });
    },
    //获取整个table用于$refs
    getData() {
      return this.tableData;
    },
    doLayout() {
      this.$refs.table.doLayout();
    }
  }
};
</script>
<style lang="scss" scoped></style>
